#ifndef _MINIRPC_WRAPPER_H_
#define _MINIRPC_WRAPPER_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
    typedef void (*rpc_request_func)(void* self, void* ex, void* user_ctx);

    typedef void (*rpc_session_callback)(void* self, void* user_ctx);
    struct rpc; 
    struct msg_base
    {
        uint32_t msg_len; //msg_total_len
        uint32_t sync; //msg can be free?
        uint64_t sign; //signature
        uint32_t free;
        uint16_t rpc_type; //rpc request type
        uint16_t status_code; // rpc response status code
        
        uint32_t data_len;
        char data[0];
    };
    struct rpc_execution 
    {
        struct msg_base* req_msg;
        struct msg_base* resp_msg;

        rpc_request_func cb;
        void* cb_arg;

        int cb_error;
        uint32_t self_id;
        uint64_t trigger_req;
        uint64_t remote_addr;
        uint32_t large_msg_len;
        uint32_t finish_len;

        uint64_t time_stamp;
        void *wait_free_rpc;
        uint32_t wait_free_sess_id;

    };

    struct hash_table; //CPP STL std:unordered_map
    enum rpc_flag{
        RPC_SERVER = 0x1,
        RPC_RDMA = 0x1 << 1,
        RPC_SHARED_RECV_QUEUE = 0x1 << 2,
        RPC_PREALLOC = 0x1 << 3,
    };
    enum rpc_error{
        RPC_ERROR_SUCCESS = 0,
        RPC_ERROR_TIME_OUT,
        RPC_ERROR_UNKNOWN_SESS_ID,
        RPC_ERROR_CLOSED_SESS,
        RPC_ERROR_LACK_REMOTE_MEMORY,
        RPC_LARGE_MSG_NOTIFY,
        RPC_LARGE_MSG_FINISH,
        RPC_LARGE_MSG_NOTIFY2,
        RPC_LARGE_MSG_FINISH2,
    };
    extern struct hash_table* create_hash_table();
    extern void* hash_find(struct hash_table* t, uint64_t key);
    extern void hash_insert(struct hash_table* t, uint64_t key, void* value);
    extern void hash_delete(struct hash_table* t, uint64_t key);
    extern void destroy_hash_table(struct hash_table*);
    extern uint64_t hash_size(struct hash_table* t);
    extern uint64_t hash_get(struct hash_table* t, uint64_t index);

    struct mini_queue;
    extern void enqueue(struct mini_queue* q, void* e);
    extern void dequeue(struct mini_queue* q);
    extern struct mini_queue* create_queue();
    extern void destroy_queue(struct mini_queue* q);
    extern void* queue_front(struct mini_queue* q);
    extern int queue_size(struct mini_queue* q);
    extern void queue_remove(struct mini_queue* q, void* e);

    typedef void (*rpc_request_func)(void* self, void* ex, void* user_ctx);

    typedef void (*rpc_session_callback)(void* self, void* user_ctx);
    /** rpc_create - create rpc
    * @param mem : user pre-allocate memory base address rpc would malloc() if NULL
    * @param mem_size : user pre-allocate memory size. rpc would malloc() if 0
    * @param flag : to enable specific function
    * @return : pointer to new created rpc, NULL if failed to create
    */
    extern struct rpc* rpc_create(void* mem, uint64_t mem_size, uint8_t flag, void* user_ctx);

    /** rpc_destroy - destroy rpc
    * @param self : rpc should be destroy
    */
    extern void rpc_destroy(struct rpc* self);

    /** rpc_async_bind_local_uri - SERVER bind (ip:port) and listen to it
    * @param self : rpc created (like this in C++)
    * @param uri : format is ip:port(for example 10.0.0.37:12345)
    * @return : socket created to listen, -1 if failed
    */
    extern int rpc_bind_local_uri(struct rpc* self, char* uri);

    /** rpc_async_accept_session - SERVER accept new connection
    * @param self : rpc created (like this in C++)
    */
    extern void rpc_accept_session(struct rpc* self);

    /** rpc_async_create_session - CLIENT create new session
    * @param self : rpc created (like this in C++)
    * @param uri : format is ip:port(for example 10.0.0.37:12345)
    * @return : session id if return, block forever if failed.
    */
    extern int rpc_create_session(struct rpc* self, char* uri, rpc_session_callback cb, void* cb_arg);

    /** rpc_close_session - close session and free resource
    * @param self : rpc created (like this in C++)
    * @param sess_id : session id should be closed
    */
    extern void rpc_close_session(struct rpc* self, int sess_id);

    /** rpc_register_on_request - SERVER register rpc handler for different rpc type
    * @param self : rpc created (like this in C++)
    * @param rpc_type : corresponding rpc type client set in header
    * @param request_handler : function point of rpc handler
    */
    extern void rpc_register_on_request(struct rpc* self, uint8_t rpc_type, rpc_request_func request_handler);

    /** alloc_msg_headers - alloc a msg headers
    * @param self : rpc created (like this in C++)
    * @return : headers address, NULL if there is no memory
    */
    extern void* alloc_msg_header(struct rpc* self);
    
    /** free_msg_headers - free a msg headers
    * @param self : rpc created (like this in C++)
    * @param header : header to free
    * @return : 1 if success, 0 if fail
    */
    extern int free_msg_header(struct rpc* self, void* header);
    
    extern void* alloc_large(struct rpc* self);
    extern void* free_large(struct rpc* self, void* header);
    /** prepare_msg_header - set header
    * @param self : rpc entity
    * @param header : request header to set
    * @param rpc_type : rpc type of handler would be called at rpc server
    * @param req_addr : data address that rpc server would read from rpc client
    * @param req_len : data length that rpc server read from rpc client
    * @param resp_addr : data address that rpc server write to rpc client
    * @param resp_len : data length that rpc server write to rpc client
    * @return : inline data reference
    */
    extern void* prepare_msg_header(struct rpc* self, struct msg_base* header, uint8_t rpc_type, uint64_t req_addr, uint32_t req_len, uint64_t resp_addr, uint32_t resp_len);
    

    extern int enqueue_request(struct rpc* self, int sess, struct msg_base* header, rpc_request_func cb , void* cb_arg, int batching);
    /** run_poll_once - data path main event loop
    * @param self : rpc created (like this in C++)
    * @return : -1 : session closed, re-create, 1 : enqueue successfully
    */
    extern int run_poll_once(struct rpc* self);

    extern void* alloc_small_piece(struct rpc* self);
    extern void free_small_piece(struct rpc* self, void* piece);
    extern void rpc_handle_complete(struct rpc* self, struct rpc_execution* ex);

    extern struct msg_base* get_request_header(struct rpc_execution* ex);
    extern struct msg_base* get_response_header(struct rpc_execution* ex);
    extern int get_error_code(struct rpc_execution* ex);

    extern double to_seconds(uint64_t cycles, double cycles_per_sec);
    extern uint64_t to_microseconds(uint64_t cycles, double cycles_per_sec);
    extern uint64_t rdtsc(); 

    extern void alloc_remote_large_buf(struct msg_base* msg);
#ifdef __cplusplus
};
#endif

#endif // !_MINIRPC_WRAPPER_H_